﻿#region Copyright © 2013 Paul Miller (Millz)
// All rights are reserved. Reproduction or transmission in whole or in part,
// any form or by any means, electronic, mechanical or otherwise, is prohibited
// without the prior written consent of the copyright owner.
// 
// Filename: Unit.cs
// Date:     23/09/2013
// Author:   Paul Miller (Millz)
#endregion

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using CommonBehaviors.Actions;
using Insanity.Helpers;
using Insanity.Managers;
using Insanity.Settings;
using Styx;
using Styx.CommonBot;
using Styx.CommonBot.POI;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;

namespace Insanity.Core
{
    internal static class Unit
    {
        #region Core Unit Checks - these should all be incorperated into the individual class checks below.

        private static LocalPlayer Me { get { return StyxWoW.Me; } }

        internal static IEnumerable<WoWUnit> AttackableUnits
        {
            get
            {
                    var unitList = ObjectManager.GetObjectsOfTypeFast<WoWUnit>().Where(
                        u => u.IsValid && !u.IsDead && !u.IsNonCombatPet && !u.IsCritter && u.Distance <= 120);

                    if (Me.InBgOrArena())
                    {
                        return unitList.Where(u =>
                            u.Attackable &&
                            u.CanSelect &&
                            !SpellLists.PvPIgnoreUnits.Contains(u.Entry) &&
                            !u.IsFriendly);
                    }

                    return
                        unitList.Where(
                            u =>
                                !u.IsFriendly && u.Attackable && u.CanSelect &&
                                !SpellLists.IgnoreUnits.Contains(u.Entry));
                }
        }


        internal static IEnumerable<WoWUnit> NearbyAttackableUnits(WoWPoint fromLocation, double radius)
        {
            var hostile = AttackableUnits;
            var maxDistance = radius*radius;
            return hostile.Where(x => x.Location.DistanceSqr(fromLocation) < maxDistance && !x.IsCrowdControlled());
        }

        #endregion

        #region Cached methods

        internal static IEnumerable<WoWUnit> CachedNearbyAttackableUnits(WoWPoint fromLocation, double radius)
        {
            var hostile = CachedUnits.CachedAttackableUnits;
            var maxDistance = radius * radius;

            if (hostile == null || hostile.Count == 0)
            {
                hostile = NearbyAttackableUnits(fromLocation, radius).ToList();
            }
            return hostile.Where(x => x != null && x.IsValid && x.Location.DistanceSqr(fromLocation) < maxDistance);
        }

        internal static IEnumerable<WoWUnit> CachedNearbyAttackableUnitsAttackingUs(WoWPoint fromLocation, double radius)
        {
            var hostile = CachedUnits.CachedAttackableUnits;
            var maxDistance = radius * radius;

            if (hostile == null || hostile.Count == 0)
            {
                hostile = NearbyAttackableUnits(fromLocation, radius).ToList();
            }

            return hostile.Where(x => x.IsValid && x.Location.DistanceSqr(fromLocation) < maxDistance && (Me.InBgOrArena() || Me.ZoneId == 6852 /*provind ground*/ || x.IsTargetingUs()));
        }

        #endregion Cached methods
        
        #region MultiDotting

        internal static WoWUnit GetMultiDoTTarget(WoWUnit unit, string debuff, double radius, double refreshDurationRemaining)
        {
            // find unit without our debuff
            var dotTarget = CachedNearbyAttackableUnitsAttackingUs(unit.Location, radius)
                .Where(x => x != null && !x.IsTotem())
                .OrderByDescending(x => x.HealthPercent)
                .FirstOrDefault(x => !x.HasMyAura(debuff) && x.InLineOfSpellSight);

            if (dotTarget == null)
            {
                // If we couldn't find one without our debuff, then find ones where debuff is about to expire.
                dotTarget = CachedNearbyAttackableUnitsAttackingUs(unit.Location, radius)
                            .Where(x => x != null && !x.IsTotem())
                            .OrderByDescending(x => x.HealthPercent)
                            .FirstOrDefault(x => (x.HasMyAura(debuff) && Spell.GetMyAuraTimeLeft(debuff, x) < refreshDurationRemaining) && x.InLineOfSpellSight);
            }
            return dotTarget;
        }

        #endregion MultiDotting

        #region Extensions

        internal static bool IsCrowdControlled(this WoWUnit unit)
        {
            if (unit != null)
            {
                Dictionary<string, WoWAura>.ValueCollection auras = unit.Auras.Values;

                return auras.Any(a => a.IsHarmful && 
                    (SpellLists.CrowdControlList.Contains(a.SpellId) ||
                    
                    (a.Spell.Mechanic == WoWSpellMechanic.Banished ||
                    a.Spell.Mechanic == WoWSpellMechanic.Disoriented ||
                    a.Spell.Mechanic == WoWSpellMechanic.Charmed ||
                    a.Spell.Mechanic == WoWSpellMechanic.Horrified ||
                    a.Spell.Mechanic == WoWSpellMechanic.Incapacitated ||
                    a.Spell.Mechanic == WoWSpellMechanic.Polymorphed ||
                    a.Spell.Mechanic == WoWSpellMechanic.Sapped ||
                    a.Spell.Mechanic == WoWSpellMechanic.Shackled ||
                    a.Spell.Mechanic == WoWSpellMechanic.Asleep ||
                    a.Spell.Mechanic == WoWSpellMechanic.Frozen ||
                    a.Spell.Mechanic == WoWSpellMechanic.Invulnerable ||
                    a.Spell.Mechanic == WoWSpellMechanic.Invulnerable2 ||
                    a.Spell.Mechanic == WoWSpellMechanic.Turned ||
                    a.Spell.Mechanic == WoWSpellMechanic.Fleeing))
                                                    
                                                    );
            }
            return false;
        }

        internal static bool IsBossOrPlayer(this WoWUnit thisUnit)
        {
            return thisUnit != null && ((thisUnit.IsPlayer && !thisUnit.IsFriendly) || thisUnit.IsBoss || BossList.BossIds.Contains(thisUnit.Entry));
        }

        internal static bool IsTotem(this WoWUnit unit)
        {
            return unit != null &&
                   (unit.IsTotem || unit.Name.Contains("Totem") || SpellLists.TotemList.Contains(unit.Entry));
        }

        internal static bool IsRooted(this WoWUnit unit)
        {
            return unit != null &&
                   (unit.Rooted || unit.HasAuraWithMechanic(WoWApplyAuraType.ModRoot));
        }
        
        #endregion

        #region Aquire Units

        public static bool IsHealer(this WoWUnit unit)
        {
            if (unit == null || !unit.IsValid) return false;

            return unit.GetRole() == Role.Healer;
        }

        public static bool IsRanged(this WoWUnit unit)
        {
            if (unit == null || !unit.IsValid) return false;

            return unit.GetRole() == Role.Ranged;
        }

        public static bool IsMelee(this WoWUnit unit)
        {
            if (unit == null || !unit.IsValid) return false;

            if (unit.Guid != Me.Guid) return unit.GetRole() == Role.Melee;

            switch (StyxWoW.Me.Class)
            {
                case WoWClass.Warrior:
                    return true;
                case WoWClass.Paladin:
                    return StyxWoW.Me.Specialization != WoWSpec.PaladinHoly;
                case WoWClass.Hunter:
                    return false;
                case WoWClass.Rogue:
                    return true;
                case WoWClass.Priest:
                    return false;
                case WoWClass.DeathKnight:
                    return true;
                case WoWClass.Shaman:
                    return StyxWoW.Me.Specialization == WoWSpec.ShamanEnhancement;
                case WoWClass.Mage:
                    return false;
                case WoWClass.Warlock:
                    return false;
                case WoWClass.Druid:
                    return StyxWoW.Me.Specialization != WoWSpec.DruidRestoration &&
                           StyxWoW.Me.Specialization != WoWSpec.DruidBalance;
                case WoWClass.Monk:
                    return StyxWoW.Me.Specialization != WoWSpec.MonkMistweaver;
                default:
                    return false;
            }

        }

        public enum Role
        {
            Melee,
            Ranged,
            Healer,
            Unknown
        }

        private static Role GetRole(this WoWUnit target)
        {
            if (target == null || !target.IsValid) return Role.Unknown;

            // Force identify unit as a healer if they're casting healing spells (i.e. trying to self heal)
            if (target.IsCastingHealingSpell) return Role.Healer;

            switch (target.Class)
            {
                case WoWClass.DeathKnight:
                    return Role.Melee;
                case WoWClass.Druid:
                    if (target.HasAnyAura(new HashSet<int> {106735, 48500})) // Restoration Overrides Passive 
                        return Role.Healer;
                    return target.MaxMana < 40000 ? Role.Melee : Role.Ranged;
                case WoWClass.Hunter:
                    return Role.Melee; // Treat it as melee
                case WoWClass.Mage:
                    return Role.Ranged;
                case WoWClass.Monk:
                    if (target.HasAnyAura(new HashSet<int> {117907, 115070}))
                        return Role.Healer;
                    return Role.Melee;
                case WoWClass.Paladin:
                    if (target.HasAnyAura(new HashSet<int> {88821, 76669}) ||
                        target.MaxMana > target.MaxHealth/2)
                        return Role.Healer;
                    return Role.Melee;
                case WoWClass.Priest:
                    if (target.HasAnyAura(new HashSet<int> {114884, 15473}))
                        //Vampiric Touch <DND> || Shadowform
                        return Role.Ranged;
                    return Role.Healer;
                case WoWClass.Rogue:
                    return Role.Melee;
                case WoWClass.Shaman:
                    if (target.HasAura(16213)) //Purification
                        return Role.Healer;
                    if (target.MaxMana < 40000)
                        return Role.Melee;
                    return Role.Ranged;
                case WoWClass.Warlock:
                    return Role.Ranged;
                case WoWClass.Warrior:
                    return Role.Melee;

                default:
                    return Role.Unknown;
            }
        }

        #endregion

        #region Targeting

        public static bool IsTargetingUs(this WoWUnit unit)
        {
            return (unit.IsTargetingMyPartyMember || unit.IsTargetingMeOrPet || unit.IsTargetingAnyMinion ||
                    unit.IsTargetingMyRaidMember || unit.IsTargetingPet || unit.IsTargetDummy());
        }

        public static bool IsTargetDummy(this WoWUnit unit)
        {

            return unit != null && SpellLists.TargetDummies.Contains(unit.Entry);
        }

        #endregion

    }
}
